<!--
   
   Copyright (c) 2017, the Perspective Authors.
   
   This file is part of the Perspective library, distributed under the terms of
   the Apache License 2.0.  The full license can be found in the LICENSE file.

-->

<!DOCTYPE html>
<html>

<head>

    <meta 
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

        <script src="/node_modules/@finos/perspective-viewer"></script>
        <script src="/node_modules/@finos/perspective-viewer-datagrid"></script>
        <script src="/node_modules/@finos/perspective-viewer-d3fc"></script>
    
        <script src="/node_modules/@finos/perspective"></script>
    
        <link rel='stylesheet' href="/node_modules/@finos/perspective-viewer/dist/umd/material.css" is="custom-style">
    

    <style>
    
        perspective-viewer {
            flex: 1;
            margin: 24px;
            overflow: visible;

            --plugin--box-shadow: 0px 4px 3px rgba(0, 0, 0, 0.1);
            --plugin--border: 1px solid #ddd;

            --d3fc-positive--gradient: linear-gradient(
                #94D0FF,
                #8795E8,
                #966bff,
                #AD8CFF,
                #C774E8,
                #c774a9,
                #FF6AD5,
                #ff6a8b,
                #ff8b8b,
                #ffa58b,
                #ffde8b,
                #cdde8b,
                #8bde8b,
                #20de8b
            );

        }

        perspective-viewer.macsoft {
            --d3fc-positive--gradient: linear-gradient(
                #1b4247,
                #09979b,
                #75d8d5,
                #ffc0cb,
                #fe7f9d,
                #65323e
            );

        }

        #app {
            display: flex;
            flex-direction: column;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #eee
        }

        #controls {
            display: flex;
            margin: 24px 24px 0px 40px;
        }

        .range {
            position: relative;
            display: inline-flex;
            flex-direction: column;
            margin-right: 24px;
        }

        span, input, button {
            font-family: "Open Sans";
            font-size: 12px;
            background: none;
            margin: 0px;
            border-color: #ccc;
            color: #666;
            padding: 6px 12px 6px 0px;
        }

        input {
            height: 14px;
            border-width: 0px;
            border-style: solid;
            border-bottom-width: 1px;
            color: inherit;
            outline: none;
        }

        input[type=range] {
            margin-top: 2px;
        }

        input[type=number] {
            font-family: "Roboto Mono";
        }

        input:focus {
            border-color: #1a7da1;
        }

        input::placeholder {
            color: #ccc;
        }
        
        button {
            border: 1px solid #ccc;
            text-transform: uppercase;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            padding-left: 12px;
            height: 28px;
            outline: none;
        }

        button:hover {
            cursor: pointer;
        }

        #run {
            justify-self: center;
            margin-right: 24px;
            height: 83px;
            width: 80px;
        }

    </style>
</head>

<body>

    <div id="app">
        <div id="controls">
            <button id="run">Run</button>
            <div class="range">
                <span>Size</span>
                <input id="width" min="25" max="300" type="number" placeholder="Width" value="100"></input>
                <input id="height" min="25" max="300" type="number" placeholder="Height" value="50"></input>
            </div>
            <div class="range">
                <span id="xrange">X [-0.4 , -0.1]</span>
                <input id="xmin" min="-2" max="1.0" step="0.1" value="-0.4" type="range"></input>
                <input id="xmax" min="-2" max="1.0" step="0.1" value="-0.1" type="range"></input>
            </div>
            <div class="range">
                <span id="yrange">Y [-0.7 , -0.6]</span>
                <input id="ymin" min="-1" max="1.0" step="0.1" value="-0.7" type="range"></input>
                <input id="ymax" min="-1" max="1.0" step="0.1" value="-0.6" type="range"></input>
            </div>
            <div class="range">
                <span>Iterations</span>
                <input id="iterations" min="1" max="1000"type="number" placeholder="Iterations" value="60"></input>
                <input id="batch" min="1" max="1000" type="number" placeholder="Batch Size" value="1"></input>
            </div>
            <div class="range">
                <span>Theme</span>
                <button id="theme">Macsoft</button>
            </div>
        </div>

        <perspective-viewer 
            id="viewer"
            class="macsoft"
            plugin="d3_heatmap" 
            row-pivots='["x"]' 
            column-pivots='["y"]' 
            columns='["c"]'>

        </perspective-viewer>
    </div>

    <script>

        // Mandelbrot calculation

        function set_defaults(iterations, args = {}) {
            args.vx = args.vx || 0.0;
            args.vy = args.vy || 0.0;
            args.vxx = args.vxx || 0;
            args.vyy = args.vyy || 0;
            args.vxy = args.vxy || 0;
            args.c = args.c || iterations;
        }
        
        function iter_args(cx, cy, iterations, args = {}) {
            if (args.vxx + args.vyy <= 4) {
                args.vxy = args.vx * args.vy;
                args.vxx = args.vx * args.vx;
                args.vyy = args.vy * args.vy;
                args.vx = args.vxx - args.vyy + cx;
                args.vy = args.vxy + args.vxy + cy;
                args.c--;
            }
            return args;
        }

        function mandel_step(json, ix, iy, xmin, xmax, ymin, ymax, width, height, iterations) {
            const index = ix * height + iy;
            const args = json[index];
            const x = xmin + ((xmax - xmin) * ix) / (width - 1);
            const y = ymin + ((ymax - ymin) * iy) / (height - 1);
            set_defaults(iterations, args);
            const new_args = iter_args(x, y, iterations, args);
            new_args.x = ix;
            new_args.y = iy;
            json[index] = new_args;
        }

        function *mandel_iter(width, height, batch) {           
            for (let bi = 0; bi < batch; ++bi) {
                for (let ix = 0; ix < width; ++ix) {
                    for (let iy = 0; iy < height; ++iy) {
                        yield [ix, iy];
                    }
                }
            }                   
        }

        async function mandelbrot(table, view, xmin, xmax, ymin, ymax, width, height, iterations, batch) {
            const run = document.getElementById("run");
            let json = [];
            try {
                for (let ii = 0; ii < iterations / batch; ++ii) {
                    for (const [ix, iy] of mandel_iter(width, height, batch)) {
                        mandel_step(json, ix, iy, xmin, xmax, ymin, ymax, width, height, iterations);
                    }
                    table.replace(json);
                    json = await view.to_json();
                    run.innerHTML = `Stop (${ii * batch}/${iterations})`;
                }
            } finally {
                run.innerHTML = `Run`;                
            }
            view.delete();
        }

        // GUI

        const SCHEMA = {
            x: "integer",
            y: "integer",
            vx: "float",
            vy: "float",
            vxx: "float",
            vyy: "float",
            vxy: "float",
            c: "float"
        };

        function make_range(x, y, range, name) {
            const title = () => 
                name + " [" + 
                x.valueAsNumber.toFixed(1) + ", " + 
                y.valueAsNumber.toFixed(1) + "]";

            x.addEventListener("input", () => { 
                x.value = Math.min(x.valueAsNumber, y.valueAsNumber - 0.1);
                range.innerHTML = title();
            });
            
            y.addEventListener("input", () => { 
                y.value = Math.max(x.valueAsNumber + 0.1, y.valueAsNumber);
                range.innerHTML = title();
            });
        }

        const make_theme_click_callback = (theme, viewer) => () => {
            if (theme.innerHTML === "Vaporwave") {
                theme.innerHTML = "Macsoft";
                viewer.classList.add("macsoft");
            } else {
                theme.innerHTML = "Vaporwave";
                viewer.classList.remove("macsoft");
            }
        }

        const make_run_click_callback = (worker, state) => async () => {
            if (window.run.innerHTML.trim() !== "Run") {
                state.view.delete();
                window.run.innerHTML = "Run";
                return;
            }
            window.run.innerHTML = `Stop (0/${window.iterations.valueAsNumber})`;
            const old_table = state.table;
            if (state.view) {
                state.view.delete();
            }
            state.table = worker.table(SCHEMA);
            state.view = state.table.view();
            mandelbrot(state.table, state.view, ...[
                "xmin", 
                "xmax", 
                "ymin", 
                "ymax", 
                "width", 
                "height",
                "iterations", 
                "batch"
            ].map(x => window[x].valueAsNumber));
            window.viewer.load(state.table);
            if (old_table) {
                old_table.delete();
            }
        }

        // Main

        window.addEventListener("WebComponentsReady", async function() {
            make_range(xmin, xmax, xrange, "X");
            make_range(ymin, ymax, yrange, "Y");

            theme.addEventListener("click", make_theme_click_callback(theme, viewer));
            run.addEventListener("click", make_run_click_callback(window.perspective.worker(), {}));
            run.dispatchEvent(new Event("click"));
        });

    </script>

</body>

</html>